package com.yvon.maple.service.base.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.google.common.base.Strings;
import com.google.common.collect.Sets;
import com.yvon.boot.common.result.GlobalException;
import com.yvon.boot.mybatis.BaseServiceImpl;
import com.yvon.maple.service.base.entity.FileInfo;
import com.yvon.maple.service.base.mapper.FileInfoMapper;
import com.yvon.maple.service.base.oss.Storage;
import com.yvon.maple.service.base.service.FileInfoService;
import com.yvon.maple.service.base.service.FileRelateService;
import com.yvon.maple.service.base.service.VideoService;
import com.yvon.maple.service.base.util.FileUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


/**
 * 文件信息服务impl
 *
 * @author : yvonwang
 * @since : 2022/08/13
 */
@Slf4j
@Service
public class FileInfoServiceImpl extends BaseServiceImpl<FileInfo, FileInfoMapper> implements FileInfoService {

    private static final String BUCKET_NAME = "kulalasmile";

    String projectUrl = System.getProperty("user.dir").replaceAll("\\\\", "/");

    @Resource
    Storage storage;

    @Value("${oss.type}")
    private String storageType;

    @Value("${oss.minio.endpoint}")
    private String endpoint;

    /**
     * 签名时效（单位：小时）
     */
    @Value("#{${oss.temp.expiry}}")
    private int signExpiry;

    @Autowired
    private FileRelateService fileRelateService;

    @Resource
    private VideoService videoService;

    @Resource(name = "ossUploadTreadPool")
    private ThreadPoolTaskExecutor poolTaskExecutor;

    @Override
    public FileInfo putFile(MultipartFile file)  {
        FileInfo fileInfo = of(file);
        save(fileInfo);
        // 上传文件
        try {
            storage.putObject(fileInfo.getBucketName(), fileInfo.getFileName(), file.getInputStream(), fileInfo.getContentType());
        } catch (Exception e) {
            throw new GlobalException(e, "文件上传失败");
        }
        return fileInfo;
    }

    @Override
    public FileInfo putVideo(MultipartFile video) {
        FileInfo fileInfo = of(video);
        String m3u8File = videoService.mediaFileToM3u8(video, fileInfo);
        try {
            String upload2M3u8 = upload2M3u8(m3u8File, fileInfo);
            fileInfo.setTempUrl(upload2M3u8);
        }catch (Exception e) {
            throw new GlobalException(e, "视频文件上传失败");
        }
        save(fileInfo);
        return fileInfo;
    }

    public String upload2M3u8(String path, FileInfo fileInfo) throws Exception {
        //存储转码后文件
        String realPath = path.substring(0, path.lastIndexOf("/"));
        log.info("视频解析后的 realPath: [{}]", realPath);
        String name = fileInfo.getFileName();
        log.info("解析后视频 nam: [{}]", name);
        File allFile = new File(realPath);
        File[] files = allFile.listFiles();
        if (null == files || files.length == 0) {
            return null;
        }
        String patch = fileInfo.getId() + "/";
        List<File> errorFile = new ArrayList<>();

        long start = System.currentTimeMillis();
        //开始上传
        CountDownLatch countDownLatch = new CountDownLatch(files.length);
        Arrays.stream(files).forEach(li -> poolTaskExecutor.execute(() -> {
            try (FileInputStream fileInputStream = new FileInputStream(li)) {
                storage.putObject(fileInfo.getBucketName(), patch + li.getName(), fileInputStream, "application/octet-stream");
                log.info("文件：{} 正在上传", li.getName());
            } catch (Exception e) {
                errorFile.add(li);
                e.printStackTrace();
            } finally {
                countDownLatch.countDown();
            }
        }));
        countDownLatch.await();
        long end = System.currentTimeMillis();
        log.info("解析文件上传成功,共计：{} 个文件,失败：{},共耗时： {}ms", files.length, errorFile.size(), end - start);

        //异步移除所有文件
        poolTaskExecutor.execute(() -> FileUtils.deleteFiles(projectUrl +"/" + fileInfo.getId()+ "/"));
        if (CollectionUtils.isEmpty(errorFile)) {
            return endpoint + "/" + fileInfo.getBucketName() +"/"+ patch + name;
        }
        return "";
    }




    @Override
    public List<FileInfo> putFiles(List<MultipartFile> files) {
        List<FileInfo> fileInfos = new ArrayList<>();
        files.forEach(file -> {
            FileInfo fileInfo = of(file);
            save(fileInfo);
            // 上传文件
            try {
                storage.putObject(fileInfo.getBucketName(), fileInfo.getFileName(), file.getInputStream(), fileInfo.getContentType());
            } catch (Exception e) {
                throw new GlobalException(e, "文件上传失败");
            }
            fileInfos.add(fileInfo);
        });
        return  fileInfos;
    }



    @Override
    public InputStream getFileStream(FileInfo fileInfo) throws Exception {
        Assert.isTrue(storageType.equals(fileInfo.getStorage()), String.format("暂时只能下载[%s]存储的文件", storageType));
        return storage.getObject(fileInfo.getBucketName(), fileInfo.getFileName());
    }


    @Override
    public boolean removeById(Serializable id) {
        FileInfo fileInfo = baseMapper.selectById(id);
        baseMapper.deleteById(id);
        try {
            storage.removeObject(BUCKET_NAME.toLowerCase(), fileInfo.getFileName());
        } catch (Exception e) {
            throw new GlobalException(e, "文件删除失败");
        }
        return true;
    }

    @Override
    public boolean removeByIds(Collection idList) {
        this.baseMapper.deleteBatchIds(idList);
        List<FileInfo> fileInfos = baseMapper.selectBatchIds(idList);
        try {
            storage.removeObjects(BUCKET_NAME.toLowerCase(), fileInfos.stream().map(FileInfo::getFileName).collect(Collectors.toList()));
        } catch (Exception e) {
            throw new GlobalException(e, "文件删除失败");
        }
        return true;
    }

    @SneakyThrows
    @Override
    public FileInfo getById(Serializable id) {
        FileInfo fileInfo = this.baseMapper.selectById(id);
        if (Objects.nonNull(fileInfo)) {
            fileInfo.setTempUrl(storage.getTempUrl(fileInfo.getBucketName(), fileInfo.getFileName(), signExpiry, TimeUnit.HOURS));
            fileInfo.setExpiry(signExpiry);
        }
        return fileInfo;
    }


    @Override
    public List<String> tempUrl(Set<String> businessKeys, Integer hour) {
        List<String> urls = new ArrayList<>();
        List<Long> fileIds = fileRelateService.getFileIdsByBusinessKeys(businessKeys);
        int duration = Objects.nonNull(hour) ? hour : signExpiry;
        if (CollUtil.isNotEmpty(fileIds)) {
            fileIds.forEach(id -> {
                try {
                    urls.add(storage.getTempUrl(BUCKET_NAME.toLowerCase(), id.toString(), duration, TimeUnit.HOURS));
                } catch (Exception e) {
                    log.warn("文件临时链接获取失败: {}", e.getMessage(), e);
                }
            });
        }
        return urls;
    }

    @Override
    public List<String> getTempUrl(Set<Long> ids, Integer hour) {
        List<String> urls = new ArrayList<>();
        int duration = Objects.nonNull(hour) ? hour : signExpiry;
        if (CollUtil.isNotEmpty(ids)) {
            List<FileInfo> fileInfoList = lambdaQuery().in(FileInfo::getId, ids)
                    .list();
            if (CollUtil.isNotEmpty(fileInfoList)) {
                fileInfoList.forEach(fileInfo -> {
                    try {
                        urls.add(storage.getTempUrl(fileInfo.getBucketName(), fileInfo.getFileName(), duration, TimeUnit.HOURS));
                    } catch (Exception e) {
                        log.warn("文件临时链接获取失败: {}", e.getMessage(), e);
                    }
                });
            }
        }
        return urls;
    }

    @Override
    public String getTempUrl(Long id, Integer hour) {
        FileInfo fileInfo = getById(id);
        if (Objects.nonNull(fileInfo)) {
            int duration = Objects.nonNull(hour) ? hour : signExpiry;
            try {
                return storage.getTempUrl(fileInfo.getBucketName(), fileInfo.getFileName(), duration, TimeUnit.HOURS);
            } catch (Exception e) {
                log.warn("文件临时链接获取失败: {}", e.getMessage(), e);
            }
        }
        return null;
    }

    public FileInfo getTempUrlByHour(Long id, Integer hour) {
        FileInfo fileInfo = getById(id);
        if (Objects.nonNull(fileInfo)) {
            try {
                String tempUrl = storage.getTempUrl(fileInfo.getBucketName(), fileInfo.getFileName(), hour, TimeUnit.HOURS);
                fileInfo.setTempUrl(tempUrl);
                fileInfo.setExpiry(hour);
            } catch (Exception e) {
                log.warn("文件临时链接获取失败: {}", e.getMessage(), e);
            }
        }
        return fileInfo;
    }

    @Override
    public List<FileInfo> getFileInfo(Set<String> businessKeys, Integer hours) {
        int duration = Objects.nonNull(hours) ? hours : signExpiry;
        List<Long> fileIds = fileRelateService.getFileIdsByBusinessKeys(businessKeys);
        List<FileInfo> fileInfoList = new ArrayList<>();
        if (CollUtil.isNotEmpty(fileIds)) {
            fileIds.forEach(fileId-> {
                fileInfoList.add(getTempUrlByHour(fileId, duration));
            });
        }
        return fileInfoList;
    }

    @Override
    public Boolean incrSave(Set<Long> fileIds, String businessKey) {
        return fileRelateService.incrSave(businessKey, fileIds);
    }

    @Override
    public Boolean coverSave(Set<Long> fileIds, String businessKey) {
        return fileRelateService.coverSave(businessKey, fileIds);
    }

    @Override
    public List<String> getTempUrlByBusiness(Set<String> businessKeys, Integer hours) {
        List<Long> ids = fileRelateService.getFileIdsByBusinessKeys(businessKeys);
        List<String> tempUrlList = new ArrayList<>();
        if (Objects.nonNull(ids)) {
            ids.forEach(id-> tempUrlList.add(getTempUrl(id, hours)));
        }
        return tempUrlList;
    }

    @Override
    public Map<String, List<String>> tempUrlMap(Set<String> businessKeys, Integer hours) {
        Map<String, List<String>> map = new HashMap<>(10);
        if (CollUtil.isNotEmpty(businessKeys)) {
            businessKeys.forEach(businessKey -> {
                List<Long> fileIds = fileRelateService.getFileIdsByBusinessKey(businessKey);
                List<String> tempUrl = getTempUrl(Sets.newHashSet(fileIds), null);
                map.put(businessKey, tempUrl);
            });
        }
        return map;
    }


    /**
     * 构建文件信息
     *
     * @param file     文件
     * @return 文件信息
     * @author : Yvon / 2020-02-17
     */
    @SneakyThrows
    private FileInfo of(MultipartFile file) {
        final String oriFileName = file.getOriginalFilename();
        long id = IdWorker.getId();
        String fileName = String.valueOf(id);
        String fileSuffix = oriFileName.substring(oriFileName.lastIndexOf(".") + 1);
        if (!Strings.isNullOrEmpty(fileSuffix)) {
            fileName = id + StrUtil.DOT.concat(fileSuffix);
        }
        FileInfo fileInfo = FileInfo.builder()
                .bucketName(BUCKET_NAME.toLowerCase())
                .fileName(fileName)
                .fileOriName(oriFileName)
                .fileSize(file.getSize())
                .contentType(file.getContentType())
                .storage(storageType)
                .build();
        fileInfo.setId(id);
        return fileInfo;
    }

}
